/* Copyright 2008. All rights reserved. */
package org.bling.swt;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;

/*
 * <code>BlingLayout</code> is a customer SWT Layout that is meant to behave similar to HTML.
 * Items are naturally displayed on the screen horizontally left to right, and
 * wrapped to the next 'line' if the widget will not fit on the current width
 * of the Shell.
 */
public final class BlingLayout extends Layout {

	private static final Logger LOG = LogManager
	.getLogger(BlingLayout.class);
	
  /**
   * Constructs a new instance of this class.
   */
  public BlingLayout() {
  }

  /*
   * Computes and returns the size of the specified composite's client area
   * according to this layout.
   * 
   * This method computes the size that the client area of the composite must be
   * in order to position all children at their preferred size inside the
   * composite according to the layout algorithm encoded by this layout.
   * 
   * When a width or height hint is supplied, it is used to constrain the
   * result. For example, if a width hint is provided that is less than the
   * width of the client area, the layout may choose to wrap and increase
   * height, clip, overlap, or otherwise constrain the children.
   * 
   */
  protected Point computeSize(Composite composite, int wHint, int hHint,
      boolean flushCache) {
    Point extent;
    extent = layoutHorizontal(composite, false, new Point(wHint, hHint),
        flushCache);
    if (wHint != SWT.DEFAULT) {
      extent.x = wHint;
    }
    if (hHint != SWT.DEFAULT) {
      extent.y = hHint;
    }
    return extent;
  }

  protected boolean flushCache(Control control) {
    return true;
  }

  /**
   * Lays out the children of the specified composite according to this layout.
   * 
   * This method positions and sizes the children of a composite using the
   * layout algorithm encoded by this layout. Children of the composite are
   * positioned in the client area of the composite. The position of the
   * composite is not altered by this method.
   * 
   * When the flush cache hint is true, the layout is instructed to flush any
   * cached values associated with the children. Typically, a layout will cache
   * the preferred sizes of the children to avoid the expense of computing these
   * values each time the widget is laid out.
   * 
   * When layout is triggered explicitly by the programmer the flush cache hint
   * is true. When layout is triggered by a resize, either caused by the
   * programmer or by the user, the hint is false.
   * 
   */
  protected void layout(Composite composite, boolean flushCache) {
    Rectangle clientArea = composite.getClientArea();
    layoutHorizontal(composite, true, new Point(clientArea.width,
        clientArea.height), flushCache);
  }

  Point computeSize(Control control, boolean flushCache) {
    int wHint = SWT.DEFAULT, hHint = SWT.DEFAULT;
    BlingLayoutData data = (BlingLayoutData) control.getLayoutData();
    if (data != null) {
      wHint = data.width;
      hHint = data.height;
    }
    return control.computeSize(wHint, hHint, flushCache);
  }

  Point layoutHorizontal(Composite composite, boolean move, Point size,
      boolean flushCache) {

    LOG.debug("----COMPOSITE LAYOUT " + composite);

    // Get Layout Data for this Composite
    BlingLayoutData layoutData = (BlingLayoutData) composite.getLayoutData();
    if (layoutData == null) {
      layoutData = new BlingLayoutData();
    }

    // Get Children for this composite
    List<Control> children = getVisibleChildren(composite);
    if (children.isEmpty()) {
      return size;
    }

    BlingLayoutHelper layoutHelper = new BlingLayoutHelper(size, new Point(0, 0),
        layoutData);

    Rectangle rect = composite.getClientArea();
    LOG.debug("****MOVE****  " + rect);
    layoutHelper = new BlingLayoutHelper(size, new Point(rect.x, rect.y), layoutData);

    Iterator<Control> childIterator = children.iterator();
    
    while (childIterator.hasNext()) {
      Control child = childIterator.next();
      BlingLayoutData childLayoutData = getCustomLayoutData(child);

      Point childSize = computeSize(child, flushCache);
      layoutHelper.add(child, childSize, childLayoutData);
    }

    return layoutHelper.getCompositeBounds();
  }

  private List<Control> getVisibleChildren(Composite composite) {
    List<Control> retval = new ArrayList<Control>();
    Control[] children = composite.getChildren();
    for (int i = 0; i < children.length; i++) {
      Control control = children[i];
      BlingLayoutData data = (BlingLayoutData) control.getLayoutData();
      if (data == null || !data.hidden) {
        retval.add(control);
      }
    }

    return retval;
  }

  private BlingLayoutData getCustomLayoutData(Control control) {
    // FIXME: How about a instanceof check to confirm this cast
    BlingLayoutData layoutData = (BlingLayoutData) control.getLayoutData();
    if (layoutData == null) {
      layoutData = new BlingLayoutData();
    }
    return layoutData;
  }

}
